#define __LIBRARY__ // 使用了_syscalln

#include <stdio.h> //使用了printf()
#include <sem.h>   //信号量的头文件
/* open(),write(),fsync(),lseek()*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

_syscall2(sem_t *, sem_open, const char *, name, unsigned int, value);
_syscall1(int, sem_wait, sem_t *, sem);
_syscall1(int, sem_post, sem_t *, sem);
_syscall1(int, sem_unlink, const char *, name);

#define name_size 32   // 信号量名字长度的最大值
#define BUFSIZE 10     // 缓冲区大小
#define CONSUMER_NUM 4 // 消费者进程数
#define MAX_NUM 500    // 为了测试方便，item_num最大为500

sem_t *empty, *full, *mutex;

// 目前的读位置，父进程把第1个消费者进程的read_place置为0;
int read_place;
int fd_rp; // 打开读位置文件的返回值, 将read_place写入文件，以让消费者进程们共享。

int fd; // 打开文件的返回值，返回0表示成功。

void producer()
{
    // 目前的写位置
    int write_place = 0;

    // 生产一个产品 item；
    int item_num = 0;

    while (item_num <= MAX_NUM)
    {
        // test是否有空闲的缓存资源
        sem_wait(empty); // P(Empty);

        // 通过互斥信号量，实现互斥访问
        sem_wait(mutex); //   P(Mutex);

        // 将item放到空闲缓存中
        lseek(fd, write_place, SEEK_SET);
        write(fd, &item_num, sizeof(int));
        write_place = lseek(fd, 0, SEEK_CUR);
        // fsync(fd); //将fd所指的文件数据，由系统缓冲区写回磁盘，以确保数据同步,linux-0.11不支持
        sync();
        item_num++;

        sem_post(mutex); // V(Mutex);

        // 增加产品资源
        sem_post(full); // V(Full);
    }
}

void consumer()
{
    int item_num;
    do
    {
        // test是否有产品资源
        sem_wait(full); // P(Full);

        // 互斥访问
        sem_wait(mutex); // P(Mutex);

        /* 从文件读取当前的读位置*/
        lseek(fd_rp, 0, SEEK_SET);
        read(fd_rp, &read_place, sizeof(int));

        /* 如果有消费者进程读到了MAX_NUM，那么之后的消费者进程就没必要读了*/
        if (read_place == -1)
        {
            sem_post(mutex);
            break;
        }

        // 从缓冲区取出1个产品赋值给item；并未实现将item从缓冲区删除
        lseek(fd, read_place, SEEK_SET);
        read(fd, &item_num, sizeof(int));

        read_place = item_num == MAX_NUM ? -1 : lseek(fd, 0, SEEK_CUR);

        /* 接下来的读位置保存到文件中 */
        lseek(fd_rp, 0, SEEK_SET);
        write(fd_rp, &read_place, sizeof(int));
        sync();

        // 在屏幕输出item
        printf("%d: %d\n", getpid(), item_num);
        fflush(stdout);

        sem_post(mutex); // V(Mutex);

        // 增加空闲的缓存资源
        sem_post(empty); // V(Empty);

    } while (item_num < MAX_NUM);

    /*
    **1.第1个消费者，因为item_num == MAX_NUM而退出循环，
    **此时full = 0, 所以其他消费者会阻塞在sem_wait(full);中
    **2.所以需要消费者通过sem_post(full);将其他消费者唤醒，并通过break;退出循环
    */
    sem_post(full);
}

int main()
{

    /* 确保每个信号量有不同的名字；*/
    char empty_name[name_size];
    char full_name[name_size];
    char mutex_name[name_size];

    int i;

    /* linux-0.11不支持snprintf
    snprintf(empty_name, name_size, "/%ld_empty", (long)getpid());
    snprintf(full_name, name_size, "/%ld_full", (long)getpid());
    snprintf(mutex_name, name_size, "/%ld_mutex", (long)getpid());
    */

    sprintf(empty_name, "/%ld_empty", (long)getpid());
    sprintf(full_name, "/%ld_full", (long)getpid());
    sprintf(mutex_name, "/%ld_mutex", (long)getpid());

    /* 创建信号量 */
    empty = sem_open(empty_name, BUFSIZE);
    full = sem_open(full_name, 0);
    mutex = sem_open(mutex_name, 1);

    // 打开一个文件作为共享缓冲区
    fd = open("share.file", O_CREAT | O_RDWR, 0666); // 0666是一个八进制数

    /* 生产者进程 */
    if (!fork())
    {
        producer();
        exit(0);
    }

    // 打开一个文件记录读位置
    fd_rp = open("read_place.txt", O_CREAT | O_RDWR, 0666);

    /* 消费者进程 */
    // int i;  不允许在这个地方定义；
    for (i = 0; i < CONSUMER_NUM; i++)
    {
        if (i = 0)
        {
            read_place = 0;
            lseek(fd_rp, 0, SEEK_SET);
            write(fd_rp, &read_place, sizeof(int));
        }
        if (!fork())
        {
            consumer();
            exit(0);
        }
    }

    // 父进程等待所有子进程结束
    while (wait(NULL) != -1)
        ;

    /* 释放任何信号量相关的资源 */
    sem_unlink(empty_name);
    sem_unlink(full_name);
    sem_unlink(mutex_name);

    // 关闭打开的文件
    close(fd);
    close(fd_rp);
    return 0;
}
